在使用 JavaScript 開發 Windows Store App 的過程中,舉凡非同步動作,例如 IO 、 AJAX , Windows Library for JavaScript ( WinJS ) 吐回的皆是 WinJS.Promise 物件。 WinJS.Promise 物件與 jQuery Deferred 物件皆是依照 CommonJS Promises/A 實作,兩者實作方式不盡相同。但在 jQuery 1.8 將 deferred.then 實作為與 deferred.pipe 相同,並將 deferred.pipe 列為 deprecated 後, WinJS.Promise 物件與 jQuery Deferred 物件越來越相像。
以依序對 A 、 B 、 C 三個網址發出 AJAX 為例:
WinJS.Promise 物件
WinJS.xhr({ url: 'A' })
.then(function() { return WinJS.xhr({ url: 'B' }); })
.then(function() { return WinJS.xhr({ url: 'C' }); })
.done(function() { /* ... */ });
jQuery Deferred 物件
$.get('A')
.then(function() { return $.get('B') })
.then(function() { return $.get('C') })
.done(function() { /* ... */ });
再以 localStorage 有值直接取用,無值 AJAX 抓取為例:
WinJS.Promise 物件
var promise;
if(localStorage.x)
promise = WinJS.Promise.as(localStorage.x);
else
promise = WinJS.xhr({ url : 'A' }).then(function(data) { /* ... */ });
jQuery Deferred 物件
var promise;
if(localStorage.x)
promise = $.when(localStorage.x);
else
promise = $.get('A').then(function(data) { /* ... */ });
差異僅在 WinJS 與 jQuery 發出 AJAX 的方法不同,以及 WinJS 採用 WinJS.Promise.as 將非 Promise 物件變數包覆為 Promise 物件, jQuery 採用 $.when 將非 Deferred 物件變數包覆為 Deferred 物件。
WinJS.Promise 物件與 jQuery Deferred 物件最大差異在於合併多個 Promise / Deferred 物件。 jQuery 以傳入一個或多個參數區分,同時賦予 $.when 「將非 Deferred 物件變數包覆為 Deferred 物件」與「合併多個 Deferred 物件」的功能。 WinJS 則另切 WinJS.Promise.join 方法進行 Promise 物件合併。
以同時發出 A 、 B 、 C 三個 AJAX ,待三者皆完成進行下一步為例:
WinJS.Promise 物件
WinJS.Promise.join([
WinJS.xhr ({ url: 'A' }),
WinJS.xhr ({ url: 'B' }),
WinJS.xhr ({ url: 'C' })])
.done(function() { /* ... */ });
jQuery Deferred 物件
$.when(
$.get('A'),
$.get('B'),
$.get('C'))
.done(function() { /* ... */ });
注意 WinJS.Promise.join 僅傳入一個為陣列的參數, $.when 則傳入多個參數。
當我們在程式碼中以陣列收集要合併的 Promise / Deferred 物件時,必須使用 apply 呼叫 $.when :
$.when.apply($, arr)
.done(function() { /* ... */ });
WinJS.Promise.join 則不受影響:
WinJS.Promise.join(arr)
.done(function() { /* ... */ });
更需注意的是當 $.when 僅傳入一個參數時,功能為「將非 Deferred 物件變數包覆為 Deferred 物件,如傳入參數已是 Deferred 物件,則直接丟回」,與傳入多個參數時,介面並不一致。故如使用陣列收集要合併的 Deferred 物件,而陣列長度可能為 1 時,需特別處理:
$.when.apply($, arr)
.done(function() {
if(arr.lengh === 1)
arguments = arr;
/* ... */
});
另一個不同是自製 Promise / Deferred 物件時, WinJS.Promise 物件一切皆在 new WinJS.Promise 時搞定。 jQuery Deferred 物件則保持比較大的彈性,另在 Deferred 物件中利用 Closure 切出一無法改變 Deferred 物件 state 的 Promise 物件:
WinJS.Promise 物件
var promise = new WinJS.Promise(function(complete, error, progress) {
setTimeout(function() {
// ...
complete('DEMO');
}, 3000);
});
jQuery Deferred 物件
var promise = (function() {
var deferred = new $.Deferred();
setTimeout(function() {
// ...
deferred.resolve('DEMO');
}, 3000);
return deferred.promise();
})();
最後一個範例也可以寫成這樣,只是另一種寫法。XD
var promise = $.Deferred(function(deferred) {
setTimeout(function() {
// ...
deferred.resolve('DEMO');
}, 3000);
}).promise();
然後一般來講 call $.Deferred() 應該是不需要 new 。
感謝 TonyQ 大神 m(__ __)m
你的另一種寫法超讚 !!!
Q的做法,可以像Vexed大這樣,只是介面稍微不一樣(in node.js):
<pre class="c" name="code">
var Q = require('q');
var promise = (function() {
var defered = Q.defer();
process.nextTick(function() {
defered.resolved('DEMO');
});
return deferred.promise;
})();
不過也提供有一點類似jQuery的方法:
<pre class="c" name="code">
var Q = require('q');
Q.promise(function(resolve, reject, notify) {
process.nextTick(function() {
resolve('DEMO');
});
})
.done(
function(m) {console.log(m);},
function(r) {console.log(r);),
function(p) {console.log(p);}
);
目前各家promise介面都還不太一樣,看看到ECMA7有沒有機會一統江山
第一個例子有拼錯...再來一次:
<pre class="c" name="code">
var Q = require('q');
var promise = (function() {
var deferred = Q.defer();
process.nextTick(function() {
deferred.resolved('DEMO');
});
return deferred.promise;
})();
謝謝費大公 m(__ __)m